package com.google.code.swingonfire.font;

import java.awt.Font;
import java.awt.FontFormatException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;

public class FontCache
{

    private static class Key
    {
        private final String family;
        private final boolean bold;
        private final boolean italic;
        private final float size;

        public Key(String family, boolean bold, boolean italic, float size)
        {
            super();

            this.family = family;
            this.bold = bold;
            this.italic = italic;
            this.size = size;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;

            result = (prime * result) + (bold ? 1231 : 1237);
            result = (prime * result) + ((family == null) ? 0 : family.hashCode());
            result = (prime * result) + (italic ? 1231 : 1237);
            result = (prime * result) + Float.floatToIntBits(size);

            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
            {
                return true;
            }

            if (obj == null)
            {
                return false;
            }

            if (getClass() != obj.getClass())
            {
                return false;
            }

            Key other = (Key) obj;

            if (bold != other.bold)
            {
                return false;
            }

            if (family == null)
            {
                if (other.family != null)
                {
                    return false;
                }
            }
            else if (!family.equals(other.family))
            {
                return false;
            }

            if (italic != other.italic)
            {
                return false;
            }

            if (Float.floatToIntBits(size) != Float.floatToIntBits(other.size))
            {
                return false;
            }

            return true;
        }

        @Override
        public String toString()
        {
            return "Key [family=" + family + ", bold=" + bold + ", italic=" + italic + ", size=" + size + "]";
        }

    }

    private static final Map<Key, Font> BASE_FONTS = new LinkedHashMap<Key, Font>();
    private static final Map<Key, Font> DERIVED_FONTS = new LinkedHashMap<Key, Font>();

    public static String register(String resource, boolean bold, boolean italic)
    {
        Font font = resolveFont(resource);

        if (font == null)
        {
            throw new IllegalArgumentException("Font not found at " + resource);
        }

        String name = font.getFamily();
        Key key = new Key(name, bold, italic, Float.NaN);

        BASE_FONTS.put(key, font);

        return name;
    }

    public static Font getFont(String family, boolean bold, boolean italic, float size)
    {
        Key key = new Key(family, bold, italic, size);
        Font font = DERIVED_FONTS.get(key);

        if (font != null)
        {
            return font;
        }

        if ((bold) && (italic))
        {
            font = BASE_FONTS.get(new Key(family, true, true, Float.NaN));

            if (font != null)
            {
                font = font.deriveFont(size);
            }
            else
            {
                font = BASE_FONTS.get(new Key(family, true, false, Float.NaN));

                if (font != null)
                {
                    font = font.deriveFont(Font.ITALIC, size);
                }
                else
                {
                    font = BASE_FONTS.get(new Key(family, false, true, Float.NaN));

                    if (font != null)
                    {
                        font = font.deriveFont(Font.BOLD, size);
                    }
                    else
                    {
                        font = BASE_FONTS.get(new Key(family, false, false, Float.NaN));

                        if (font != null)
                        {
                            font = font.deriveFont(Font.BOLD | Font.ITALIC, size);
                        }
                    }
                }
            }
        }
        else if (bold)
        {
            font = BASE_FONTS.get(new Key(family, true, false, Float.NaN));

            if (font != null)
            {
                font = font.deriveFont(size);
            }
            else
            {
                font = BASE_FONTS.get(new Key(family, false, false, Float.NaN));

                if (font != null)
                {
                    font = font.deriveFont(Font.BOLD, size);
                }
            }
        }
        else if (italic)
        {
            font = BASE_FONTS.get(new Key(family, false, true, Float.NaN));

            if (font != null)
            {
                font = font.deriveFont(size);
            }
            else
            {
                font = BASE_FONTS.get(new Key(family, false, false, Float.NaN));

                if (font != null)
                {
                    font = font.deriveFont(Font.ITALIC, size);
                }
            }
        }
        else
        {
            font = BASE_FONTS.get(new Key(family, false, false, Float.NaN));

            if (font != null)
            {
                font = font.deriveFont(size);
            }
        }

        if (font == null)
        {
            throw new IllegalArgumentException("Undefined font: " + key);
        }

        DERIVED_FONTS.put(key, font);

        return font;
    }

    private static Font resolveFont(String name)
    {
        URL url = resolveURL(name);

        if (url == null)
        {
            return null;
        }

        try
        {
            InputStream stream = url.openStream();

            try
            {
                return Font.createFont(Font.TRUETYPE_FONT, stream);
            }
            finally
            {
                stream.close();
            }
        }
        catch (IOException e)
        {
            throw new IllegalArgumentException("Failed to read font " + url, e);
        }
        catch (FontFormatException e)
        {
            throw new IllegalArgumentException("Invalid font format " + url, e);
        }
    }

    private static URL resolveURL(String name)
    {
        return FontCache.class.getClassLoader().getResource(name);
    }

}
